﻿Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Runtime.CompilerServices
Imports System.Windows.Forms
Imports System.Workflow.ComponentModel

Namespace System.Workflow.ComponentModel.Design
    Friend NotInheritable Class ItemPalette
        ' Events
        Public Custom Event Closed As EventHandler
        Public Custom Event SelectionChanged As SelectionChangeEventHandler([Of] SelectionChangeEventArgs)

        ' Methods
        Public Sub New()
            Me.items = New ItemList([Of] ItemInfo)(Me)
        End Sub

        Private Sub DestroyPalette(ByVal palette As Palette)
            If (Not palette Is Nothing) Then
                If (Not Me.Closed Is Nothing) Then
                    Me.Closed.Invoke(Me, EventArgs.Empty)
                End If
                RemoveHandler palette.LostFocus, New EventHandler(AddressOf Me.OnPaletteLostFocus)
                palette.Close
                palette.Dispose
                Me.palette = Nothing
            End If
        End Sub

        Private Sub OnPaletteLostFocus(ByVal sender As Object, ByVal e As EventArgs)
            Me.DestroyPalette([TryCast](sender,Palette))
        End Sub

        Public Sub SetFont(ByVal font As Font)
            Me.font = font
        End Sub

        Public Sub Show(ByVal location As Point)
            If (Not Me.palette Is Nothing) Then
                Me.DestroyPalette(Me.palette)
            End If
            Me.palette = New Palette(Me, location)
            Me.palette.Font = Me.font
            Me.palette.Show
            Me.palette.Focus
            AddHandler Me.palette.LostFocus, New EventHandler(AddressOf Me.OnPaletteLostFocus)
        End Sub


        ' Properties
        Public ReadOnly Property IsVisible As Boolean
            Get
                Return ((Not Me.palette Is Nothing) AndAlso Me.palette.Visible)
            End Get
        End Property

        Public ReadOnly Property Items As IList([Of] ItemInfo)
            Get
                Return Me.items
            End Get
        End Property

        Public Property SelectedItem As ItemInfo
            Get
                Return Me.selectedItem
            End Get
            Set(ByVal value As ItemInfo)
                If (Not Me.selectedItem Is value) Then
                    Dim selectedItem As ItemInfo = Me.selectedItem
                    Me.selectedItem = value
                    If (Not Me.SelectionChanged Is Nothing) Then
                        Me.SelectionChanged.Invoke(Me, New SelectionChangeEventArgs(selectedItem, Me.selectedItem))
                        If (Not Me.palette Is Nothing) Then
                            Me.palette.Invalidate
                        End If
                    End If
                End If
            End Set
        End Property


        ' Fields
        Private font As Font
        Private items As ItemList([Of] ItemInfo)
        Private palette As Palette
        Private selectedItem As ItemInfo

        ' Nested Types
        Private NotInheritable Class Palette
            Inherits Form
            ' Methods
            Public Sub New(ByVal parent As ItemPalette, ByVal location As Point)
                Me.parent = parent
                Me.enabledItems = New ItemList([Of] ItemInfo)(Me)
                Dim info As ItemInfo
                For Each info In Me.parent.items
                    Dim verb As ActivityDesignerVerb = [TryCast](info.UserData.Item(DesignerUserDataKeys.DesignerVerb),ActivityDesignerVerb)
                    If ((verb Is Nothing) OrElse verb.Enabled) Then
                        Me.enabledItems.Add(info)
                    End If
                Next
                Me.menuItemCount = Me.enabledItems.Count
                MyBase.SetStyle((ControlStyles.OptimizedDoubleBuffer Or (ControlStyles.AllPaintingInWmPaint Or (ControlStyles.SupportsTransparentBackColor Or ControlStyles.UserPaint))), True)
                MyBase.FormBorderStyle = FormBorderStyle.None
                Me.BackColor = Color.White
                MyBase.ShowInTaskbar = False
                MyBase.MaximizeBox = False
                MyBase.ControlBox = False
                MyBase.StartPosition = FormStartPosition.Manual
                Dim screen As Screen = Screen.FromPoint(location)
                Me.workingRectangle = screen.WorkingArea
                Me.PreparePalette(location)
                Me.paletteShadow = New PaletteShadow(Me)
            End Sub

            Private Sub DestroyShadow()
                If (Not Me.paletteShadow Is Nothing) Then
                    Me.paletteShadow.Close
                    Me.paletteShadow.Dispose
                    Me.paletteShadow = Nothing
                End If
            End Sub

            Private Function GetItemBounds(ByVal index As Integer) As Rectangle
                If ((index >= 0) AndAlso (index < Me.itemRectangles.Count)) Then
                    Return Me.itemRectangles.Item(index)
                End If
                Return Rectangle.Empty
            End Function

            Private Sub LayoutPalette()
                Me.itemRectangles.Clear
                Me.leftGradientRectangle = Rectangle.Empty
                [Using] graphics As Graphics = MyBase.CreateGraphics
                    Dim empty As Size = Size.Empty
                    Dim info As ItemInfo
                    For Each info In Me.enabledItems
                        Dim ef As SizeF = graphics.MeasureString(info.Text, Me.Font)
                        empty.Width = Math.Max(Convert.ToInt32(Math.Ceiling(CDbl(ef.Width))), empty.Width)
                        empty.Height = Math.Max(Convert.ToInt32(Math.Ceiling(CDbl(ef.Height))), empty.Height)
                    Next
                    empty.Width = Math.Min(empty.Width, Me.maximumTextWidth)
                    Me.maxTextHeight = empty.Height
                    Me.itemHeight = (Math.Max(Me.imageRectangle.Height, (empty.Height + 2)) + 3)
                    Me.itemWidth = ((((Me.imageRectangle.Width + (2 * Me.selectionItemMargin.Width)) + Me.leftTextMargin) + empty.Width) + Me.rightTextMargin)
                End [Using]
                Dim y As Integer = 2
                [Using] enumerator2 As Enumerator([Of] ItemInfo) = Me.enabledItems.GetEnumerator
                    Do While enumerator2.MoveNext
                        Dim current As ItemInfo = enumerator2.Current
                        Me.itemRectangles.Add(New Rectangle(2, y, Me.itemWidth, Me.itemHeight))
                        y = (y + (Me.itemHeight + (2 * Me.selectionItemMargin.Height)))
                    Loop
                End [Using]
                Me.leftGradientRectangle = New Rectangle(2, 2, &H18, (y - 4))
                Me.formRectangle = New Rectangle(0, 0, (Me.itemWidth + 4), y)
            End Sub

            Protected Overrides Sub OnClosing(ByVal e As CancelEventArgs)
                Me.DestroyShadow
            End Sub

            Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
                MyBase.OnKeyDown(e)
                If (e.KeyCode = Keys.Return) Then
                    If (Not Me.ActiveItem Is Nothing) Then
                        Try 
                            Me.parent.SelectedItem = Me.ActiveItem
                        Finally
                            Me.parent.DestroyPalette(Me)
                        End Try
                    End If
                ElseIf (e.KeyCode = Keys.Escape) Then
                    Me.parent.DestroyPalette(Me)
                ElseIf ((e.KeyCode = Keys.Up) OrElse (e.KeyCode = Keys.Down)) Then
                    Dim index As Integer = -1
                    If (Me.activeIndex <> -1) Then
                        index = Me.activeIndex
                    End If
                    If (index >= 0) Then
                        If (e.KeyCode = Keys.Up) Then
                            index -= 1
                        ElseIf (e.KeyCode = Keys.Down) Then
                            index += 1
                        End If
                    Else
                        index = 0
                    End If
                    If ((index >= 0) AndAlso (index < Me.enabledItems.Count)) Then
                        Me.SetActiveItem(index)
                    End If
                End If
            End Sub

            Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
                MyBase.OnMouseDown(e)
                Dim info As ItemInfo = Nothing
                Dim pt As New Point(e.X, e.Y)
                Dim i As Integer
                For i = 0 To Me.enabledItems.Count - 1
                    If Me.GetItemBounds(i).Contains(pt) Then
                        info = Me.enabledItems.Item(i)
                        Exit For
                    End If
                Next i
                If (Not info Is Nothing) Then
                    Try 
                        Me.parent.SelectedItem = info
                    Finally
                        Me.parent.DestroyPalette(Me)
                    End Try
                End If
            End Sub

            Protected Overrides Sub OnMouseLeave(ByVal e As EventArgs)
                MyBase.OnMouseLeave(e)
                Me.SetActiveItem(-1)
            End Sub

            Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
                MyBase.OnMouseMove(e)
                Dim pt As New Point(e.X, e.Y)
                Dim i As Integer
                For i = 0 To Me.enabledItems.Count - 1
                    If Me.GetItemBounds(i).Contains(pt) Then
                        Me.SetActiveItem(i)
                        Return
                    End If
                Next i
            End Sub

            Protected Overrides Sub OnPaint(ByVal paintArgs As PaintEventArgs)
                Dim graphics As Graphics = paintArgs.Graphics
                graphics.FillRectangle(SystemBrushes.Window, Me.formRectangle)
                graphics.DrawRectangle(SystemPens.ControlDarkDark, Me.formRectangle.X, Me.formRectangle.Y, (Me.formRectangle.Width - 1), (Me.formRectangle.Height - 1))
                [Using] brush As Brush = New LinearGradientBrush(New Point(Me.leftGradientRectangle.Left, Me.leftGradientRectangle.Top), New Point(Me.leftGradientRectangle.Right, Me.leftGradientRectangle.Top), SystemColors.Window, SystemColors.ScrollBar)
                    graphics.FillRectangle(brush, Me.leftGradientRectangle)
                End [Using]
                Dim i As Integer
                For i = 0 To Me.enabledItems.Count - 1
                    Dim itemBounds As Rectangle = Me.GetItemBounds(i)
                    If (Me.activeIndex = i) Then
                        graphics.FillRectangle(SystemBrushes.InactiveCaptionText, itemBounds.X, itemBounds.Y, (itemBounds.Width - 1), (itemBounds.Height - 1))
                        graphics.DrawRectangle(SystemPens.ActiveCaption, itemBounds.X, itemBounds.Y, (itemBounds.Width - 1), (itemBounds.Height - 1))
                    End If
                    If (Not Me.enabledItems.Item(i).Image Is Nothing) Then
                        Dim location As New Point((itemBounds.Left + 3), (itemBounds.Top + 3))
                        Dim size As Size = Me.enabledItems.Item(i).Image.Size
                        graphics.DrawImage(Me.enabledItems.Item(i).Image, New Rectangle(location, size), New Rectangle(Point.Empty, size), GraphicsUnit.Pixel)
                    End If
                    Dim boundingRect As New Rectangle((((itemBounds.Left + 20) + 5) + 2), (itemBounds.Top + 1), (Me.itemWidth - &H1D), (Me.itemHeight - 3))
                    Dim num2 As Integer = (boundingRect.Height - Me.maxTextHeight)
                    num2 = If((num2 > 0), (num2 / 2), 0)
                    boundingRect.Height = Math.Min(boundingRect.Height, Me.maxTextHeight)
                    boundingRect.Y = (boundingRect.Y + num2)
                    graphics.TextRenderingHint = TextRenderingHint.AntiAlias
                    Dim text As String = Me.enabledItems.Item(i).Text.Replace("&", "")
                    ActivityDesignerPaint.DrawText(graphics, Me.Font, [text], boundingRect, StringAlignment.Near, TextQuality.Aliased, SystemBrushes.ControlText)
                Next i
            End Sub

            Protected Overrides Sub OnVisibleChanged(ByVal e As EventArgs)
                MyBase.OnVisibleChanged(e)
                If MyBase.Visible Then
                    Me.paletteShadow.Show
                    MyBase.BringToFront
                    MyBase.Focus
                Else
                    Me.paletteShadow.Hide
                End If
            End Sub

            Private Sub PreparePalette(ByVal location As Point)
                Me.LayoutPalette
                Dim pos As Point = location
                Dim formRectangle As Rectangle = Me.formRectangle
                formRectangle.Offset(pos)
                Dim empty As Size = Size.Empty
                formRectangle.Width = (formRectangle.Width + Palette.DropShadowWidth)
                formRectangle.Height = (formRectangle.Height + Palette.DropShadowWidth)
                Dim a As Rectangle = Rectangle.Empty
                Dim screen As Screen
                For Each screen In Screen.AllScreens
                    a = Rectangle.Union(a, screen.Bounds)
                Next
                If (Me.workingRectangle.Top > formRectangle.Top) Then
                    empty.Height = (empty.Height + (Me.workingRectangle.Top - formRectangle.Top))
                ElseIf (Me.workingRectangle.Bottom < formRectangle.Bottom) Then
                    empty.Height = (empty.Height - (formRectangle.Bottom - Me.workingRectangle.Bottom))
                End If
                If (Me.workingRectangle.Left > formRectangle.Left) Then
                    empty.Width = (empty.Width + (Me.workingRectangle.Left - formRectangle.Left))
                ElseIf (Me.workingRectangle.Right < formRectangle.Right) Then
                    empty.Width = (empty.Width - (formRectangle.Right - Me.workingRectangle.Right))
                End If
                pos = (pos + empty)
                MyBase.Location = pos
                Dim path As New GraphicsPath
                path.AddRectangle(Me.formRectangle)
                MyBase.Size = Me.formRectangle.Size
                MyBase.Region = New Region(path)
            End Sub

            Private Sub SetActiveItem(ByVal index As Integer)
                If (Me.activeIndex <> index) Then
                    If (Me.activeIndex <> -1) Then
                        MyBase.Invalidate(Me.GetItemBounds(Me.activeIndex))
                    End If
                    Me.activeIndex = index
                    If (Me.activeIndex <> -1) Then
                        MyBase.Invalidate(Me.GetItemBounds(Me.activeIndex))
                    End If
                End If
            End Sub


            ' Properties
            Private ReadOnly Property ActiveItem As ItemInfo
                Get
                    If (Me.activeIndex <= -1) Then
                        Return Nothing
                    End If
                    Return Me.enabledItems.Item(Me.activeIndex)
                End Get
            End Property


            ' Fields
            Private activeIndex As Integer = -1
            Private Shared ReadOnly DropShadowWidth As Integer = 4
            Private enabledItems As ItemList([Of] ItemInfo)
            Private formRectangle As Rectangle
            Private imageRectangle As Size = New Size(20, 20)
            Private imageSize As Size = New Size(&H10, &H10)
            Private itemHeight As Integer
            Private itemRectangles As List([Of] Rectangle) = New List([Of] Rectangle)
            Private itemWidth As Integer
            Private leftGradientRectangle As Rectangle
            Private leftTextMargin As Integer = 5
            Private maximumTextWidth As Integer = 500
            Private maxTextHeight As Integer
            Private menuItemCount As Integer
            Private paletteShadow As PaletteShadow
            Private parent As ItemPalette
            Private rightTextMargin As Integer = 20
            Private selectionItemMargin As Size = New Size(1, 1)
            Private workingRectangle As Rectangle = Rectangle.Empty

            ' Nested Types
            Private NotInheritable Class PaletteShadow
                Inherits Form
                ' Methods
                Public Sub New(ByVal parent As Palette)
                    Me.parent = parent
                    MyBase.SetStyle((ControlStyles.OptimizedDoubleBuffer Or (ControlStyles.AllPaintingInWmPaint Or (ControlStyles.SupportsTransparentBackColor Or ControlStyles.UserPaint))), True)
                    MyBase.FormBorderStyle = FormBorderStyle.None
                    Me.BackColor = Color.White
                    MyBase.ShowInTaskbar = False
                    MyBase.MaximizeBox = False
                    MyBase.ControlBox = False
                    MyBase.Opacity = 0.5
                    MyBase.StartPosition = FormStartPosition.Manual
                    MyBase.Enabled = False
                    MyBase.Region = parent.Region
                    MyBase.Location = New Point((Me.parent.Location.X + Palette.DropShadowWidth), (Me.parent.Location.Y + Palette.DropShadowWidth))
                End Sub

                Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
                    MyBase.OnPaint(e)
                    Dim formRectangle As Rectangle = Me.parent.formRectangle
                    formRectangle.Offset(-Palette.DropShadowWidth, -Palette.DropShadowWidth)
                    ActivityDesignerPaint.DrawDropShadow(e.Graphics, formRectangle, Color.Black, 4, (LightSourcePosition.Top Or LightSourcePosition.Left), 0.2!, False)
                End Sub


                ' Fields
                Private parent As Palette
            End Class
        End Class
    End Class
End Namespace

